
/**
 * 3dtiles加载类
 *  大雁塔   //resource.dvgis.cn/data/3dtiles/dayanta/tileset.json
 *  合肥数据 //resource.dvgis.cn/data/3dtiles/ljz/tileset.json
 */

import Primitive from "../Primitive"

export default class Tileset extends Primitive {
  /**
   * 
   * @param {*} url 3dtiles地址
   */
  constructor(url, options = {}) {
    super()
    this.delegate = new Cesium.Cesium3DTileset({
      url: url,
      shadows: Cesium.ShadowMode.DIENABLED,
      ...options
    })

    this._modelMatrix = new Cesium.Matrix4()
    this._fragmentShader = undefined
    this._vertexShader = undefined
    this._tileVisibleCallback = undefined

    this.mounted()
  }

  get readyPromise() {
    return this.delegate.readyPromise
  }

  get vertexShader() {
    return this._vertexShader
  }

  set vertexShader(val) {
    this._vertexShader = val
  }

  get fragmentShader() {
    return this._fragmentShader
  }

  set fragmentShader(val) {
    this._fragmentShader = val
  }

  /**
   * 初始化
   */

  mounted() {

    this._vertexShader = `
      precision highp float;
      uniform mat4 u_modelViewMatrix;
      uniform mat4 u_projectionMatrix;
      attribute vec3 a_position;
      attribute vec2 a_texcoord_0;
      varying vec2 v_texcoord_0;
      void main(void) 
      {
          vec3 weightedPosition = a_position;
          weightedPosition.x += 0. + czm_frameNumber;
          vec4 position = vec4(weightedPosition, 1.0);
          position = u_modelViewMatrix * position;
          gl_Position = u_projectionMatrix * position;

          v_texcoord_0 = a_texcoord_0;
      }
    `

    this._fragmentShader = `
      precision highp float;
      uniform vec4 u_baseColorFactor;
      uniform sampler2D u_baseColorTexture;
      uniform float u_metallicFactor;
      uniform float u_roughnessFactor;
      uniform vec3 u_emissiveFactor;
      varying vec2 v_texcoord_0;
      vec3 SRGBtoLINEAR3(vec3 srgbIn) 
      {
          return pow(srgbIn, vec3(2.2));
      }
      
      vec4 SRGBtoLINEAR4(vec4 srgbIn) 
      {
          vec3 linearOut = pow(srgbIn.rgb, vec3(2.2));
          return vec4(linearOut, srgbIn.a);
      }
      
      vec3 applyTonemapping(vec3 linearIn) 
      {
      #ifndef HDR 
          return czm_acesTonemapping(linearIn);
      #else 
          return linearIn;
      #endif 
      }
      
      vec3 LINEARtoSRGB(vec3 linearIn) 
      {
      #ifndef HDR 
          return pow(linearIn, vec3(1.0/2.2));
      #else 
          return linearIn;
      #endif 
      }
      
      vec2 computeTexCoord(vec2 texCoords, vec2 offset, float rotation, vec2 scale) 
      {
          rotation = -rotation; 
          mat3 transform = mat3(
              cos(rotation) * scale.x, sin(rotation) * scale.x, 0.0, 
            -sin(rotation) * scale.y, cos(rotation) * scale.y, 0.0, 
              offset.x, offset.y, 1.0); 
          vec2 transformedTexCoords = (transform * vec3(fract(texCoords), 1.0)).xy; 
          return transformedTexCoords; 
      }
      
      #ifdef USE_IBL_LIGHTING 
      uniform vec2 gltf_iblFactor; 
      #endif 
      #ifdef USE_CUSTOM_LIGHT_COLOR 
      uniform vec3 gltf_lightColor; 
      #endif 
      void main(void) 
      {
          vec4 baseColorWithAlpha = SRGBtoLINEAR4(texture2D(u_baseColorTexture, v_texcoord_0));
          baseColorWithAlpha *= u_baseColorFactor;
          vec3 baseColor = baseColorWithAlpha.rgb;
          vec3 color = baseColor;
          color = LINEARtoSRGB(color);
          gl_FragColor = vec4(color, 1.0);
      }
   
    `

    this._vertexShader = `
      precision highp float;
      uniform mat4 u_modelViewMatrix;
      uniform mat4 u_projectionMatrix;
      uniform mat3 u_normalMatrix;
      attribute vec3 a_position;
      varying vec3 v_positionEC;
      attribute vec3 a_normal;
      varying vec3 v_normal;
      attribute float a_batchId;
      void main(void)
      {
        vec3 weightedPosition = a_position;
        vec3 weightedNormal = a_normal;
        vec4 position = vec4(weightedPosition, 1.0);
        position = u_modelViewMatrix * position;
        v_positionEC = position.xyz;
        gl_Position = u_projectionMatrix * position;
        v_normal = u_normalMatrix * weightedNormal;
      }
    `

    this._fragmentShader = `
      varying vec3 v_positionEC;
      void main(void){
        vec4 position = czm_inverseModelView * vec4(v_positionEC,1); // 位置
        float glowRange = 100.0; // 光环的移动范围(高度)
        gl_FragColor = vec4(0.2,  0.5, 1.0, 1.0); // 颜色
        gl_FragColor *= vec4(vec3(position.z / 40.0), 1.0); // 渐变
        // 动态光环
        float time = fract(czm_frameNumber / 360.0);
        time = abs(time - 0.5) * 2.0;
        float diff = step(0.005, abs( clamp(position.z / glowRange, 0.0, 1.0) - time));
        gl_FragColor.rgb += gl_FragColor.rgb * (1.0 - diff);
    }`

    this.setFragmentShader(this.fragmentShader)
  }

  /**
   * 绑定事件
   */

  bindVisibleEvent() {
    this._tileVisibleCallback && this._tileVisibleCallback()
    this._tileVisibleCallback = this.delegate.tileVisible.addEventListener(
      this._updateTile,
      this
    )
  }

  /**
   * Updates tile
   * @param tile
   * @private
   */
  _updateTile(tile) {
    let content = tile.content

    for (let i = 0; i < content.featuresLength; i++) {
      let feature = content.getFeature(i)
      let model = feature.content._model
      // sets properties
      if (this._properties && this._properties.length) {
        this._properties.forEach(property => {
          if (
            feature.hasProperty(property['key']) &&
            feature.getProperty(property['key']) === property['keyValue']
          ) {
            feature.setProperty(
              property['propertyName'],
              property['propertyValue']
            )
          }
        })
      }

      // sets customShader
      if (
        this.fragmentShader &&
        model &&
        model._sourcePrograms &&
        model._rendererResources
      ) {
        Object.keys(model._sourcePrograms).forEach(key => {
          let program = model._sourcePrograms[key]
          model._rendererResources.sourceShaders[program.fragmentShader] = this.fragmentShader
          model._rendererResources.sourceShaders[0] = this.vertexShader
        })
        model._shouldRegenerateShaders = true
      }
    }
  }

  /**
   * 
   * @param {*} angle 
   */

  startRotate(angle = .1) {
    this._rotateAmount = angle

    this.readyPromise.then(tileset => {
      let modelMatrix = Cesium.Matrix4.clone(tileset.root.transform)
      let finalMatrix = new Cesium.Matrix4()

      this._timer = setInterval(() => {
        this._heading += this._rotateAmount
        if (this._heading >= 360 || this._heading <= -360) {
          this._heading = 0
        }
        let rotation = Cesium.Matrix4.fromRotationTranslation(
          Cesium.Matrix3.fromHeadingPitchRoll(
            new Cesium.HeadingPitchRoll(
              Cesium.Math.toRadians(this.heading),
              Cesium.Math.toRadians(this.pitch),
              Cesium.Math.toRadians(this.roll)
            )
          )
        )
        Cesium.Matrix4.multiply(modelMatrix, rotation, finalMatrix)
        tileset.root.transform = finalMatrix
      }, 1000 / 60)
    })
  }

  /**
   *
   */

  setRotate() {
    this.readyPromise.then(tileset => {
      let modelMatrix = tileset.root.transform
      let rotation = Cesium.Matrix4.fromRotationTranslation(
        Cesium.Matrix3.fromHeadingPitchRoll(
          new Cesium.HeadingPitchRoll(
            Cesium.Math.toRadians(this.heading),
            Cesium.Math.toRadians(this.pitch),
            Cesium.Math.toRadians(this.roll)
          )
        )
      )
      Cesium.Matrix4.multiply(modelMatrix, rotation, modelMatrix)
      tileset.root.transform = modelMatrix
    })
  }

  /**
   * 
   * @param {Cartesian3} position 
   */

  setPosition(position) {
    this.readyPromise.then(tileset => {
      let modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(position)
      let rotation = Cesium.Matrix4.fromRotationTranslation(
        Cesium.Matrix3.fromHeadingPitchRoll(
          new Cesium.HeadingPitchRoll(
            Cesium.Math.toRadians(this.heading),
            Cesium.Math.toRadians(this.pitch),
            Cesium.Math.toRadians(this.roll)
          )
        )
      )
      Cesium.Matrix4.multiply(modelMatrix, rotation, modelMatrix)
      tileset.root.transform = modelMatrix
    })
  }

  /**
   * Sets height
   * @param height
   * @param isAbsolute
   * @returns {Tileset}
   */
  setHeight(height, isAbsolute = false) {
    this.readyPromise.then(tileset => {
      let center = Cesium.Cartographic.fromCartesian(
        tileset.boundingSphere.center
      )
      let surface = Cesium.Cartesian3.fromRadians(
        center.longitude,
        center.latitude,
        center.height
      )
      let offset = Cesium.Cartesian3.fromRadians(
        center.longitude,
        center.latitude,
        isAbsolute ? height : center.height + height
      )
      let translation = Cesium.Cartesian3.subtract(
        offset,
        surface,
        new Cesium.Cartesian3()
      )
      tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation)
    })
    return this
  }

  /**
   * Sets feature FS
   * @param customShader
   * @returns {Tileset}
   */
  setFragmentShader(fragmentShader) {
    this.fragmentShader = fragmentShader
    this.bindVisibleEvent()
    return this
  }

  /**
   * Sets style
   * @param style
   * @returns {Tileset}
   */
  setStyle(style) {
    if (style && style instanceof Cesium.Cesium3DTileStyle) {
      this._style = style
      this.delegate.style = this._style
    }
    return this
  }

  /**
   * 
   * @param {*} shader fragmentShaderText 
   */
  setCustomShader(shader) {
    this.delegate.customShader = new Cesium.CustomShader({
      lightingModel: Cesium.LightingModel.UNLIT,
      fragmentShaderText: shader
    })
  }


  get modelMatrix(){
    return this._modelMatrix
  }

  set modelMatrix(val){
    this._modelMatrix = val
    this.readyPromise.then(tileset => {      
      tileset.root.transform = this._modelMatrix
    })
  }
}